package com.ta.gulimall.order.service.impl;

import cn.hutool.core.lang.TypeReference;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.ta.gulimall.common.interceptor.UserInterceptor;
import com.ta.gulimall.common.utils.RedisUtil;
import com.ta.gulimall.common.utils.system.R;
import com.ta.gulimall.common.view.vo.MemberVo;
import com.ta.gulimall.order.config.rabbitmq.MqQueueConstant;
import com.ta.gulimall.order.constant.OrderConstant;
import com.ta.gulimall.order.feign.*;
import com.ta.gulimall.order.service.OrderItemService;
import com.ta.gulimall.order.view.entity.OrderItemEntity;
import com.ta.gulimall.order.view.vo.*;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ta.gulimall.common.utils.system.PageUtils;
import com.ta.gulimall.common.utils.system.Query;

import com.ta.gulimall.order.dao.OrderDao;
import com.ta.gulimall.order.view.entity.OrderEntity;
import com.ta.gulimall.order.service.OrderService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;


@Service("orderService")
public class OrderServiceImpl extends ServiceImpl<OrderDao, OrderEntity> implements OrderService {
    @Autowired
    UserInterceptor userInterceptor;
    @Autowired
    ThreadPoolExecutor poolExecutor;
    @Autowired
    MemberFeignService memberFeignService;
    @Autowired
    CartFeignService cartFeignService;
    @Autowired
    WareFeignService wareFeignService;
    @Autowired
    RedisUtil redisUtil;
    @Autowired
    CouponFeignService couponFeignService;
    @Autowired
    ProductFeignService productFeignService;
    @Autowired
    OrderItemService orderItemService;
    @Autowired
    RabbitTemplate rabbitTemplate;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<OrderEntity> page = this.page(
                new Query<OrderEntity>().getPage(params),
                new QueryWrapper<OrderEntity>()
        );

        return new PageUtils(page);
    }

    @Override
    public OrderConfirmVo goodsConfirm() throws ExecutionException, InterruptedException {
        MemberVo memberVo = userInterceptor.userLocal.get();
        Long userId = memberVo.getId();

        OrderConfirmVo vo = new OrderConfirmVo();
        CompletableFuture<Void> addressFuture = CompletableFuture.runAsync(() -> {
            R r = memberFeignService.deliveryAddress(userId);
            List<MemberAddressVo> list = r.getData(new TypeReference<List<MemberAddressVo>>() {
            });
            vo.setAddresses(list);
        }, poolExecutor);

        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        CompletableFuture<Void> orderItemsFuture = CompletableFuture.supplyAsync(() -> {
            RequestContextHolder.setRequestAttributes(requestAttributes);
            R r = cartFeignService.checkCart();
            List<OrderItemVo> list = r.getData(new TypeReference<List<OrderItemVo>>() {
            });
            vo.setOrderItems(list);
            return list;
        }, poolExecutor).thenAcceptAsync((result -> {
            List<Long> list = result.stream().map(OrderItemVo::getSkuId).collect(Collectors.toList());
            R r = wareFeignService.listSkuWare(list);
            Map<Long, Long> map = r.getData(new TypeReference<Map<Long, Long>>() {
            });

            List<Long> newList = list.stream().filter(skuId -> !map.containsKey(skuId)).collect(Collectors.toList());
            Map<Long, Boolean> newmap = map.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, v -> v.getValue() > 0L));
            newmap.putAll(newList.stream().collect(Collectors.toMap(k -> k, v -> false)));
            vo.setStocks(newmap);
        }), poolExecutor);

        CompletableFuture.allOf(addressFuture, orderItemsFuture).get();

        vo.setOrderToken(UUID.randomUUID().toString().replaceAll("-",""));
        return vo;
    }

    @Override
    @Transactional
    public SubmitOrderResponseVo submitOrder(OrderSubmitVo submitVo) {
        MemberVo memberVo = userInterceptor.userLocal.get();

        List<OrderItemVo> orderItemVos = cartFeignService.checkCart().getData(new TypeReference<List<OrderItemVo>>() {});
        List<Long> skuIds = orderItemVos.stream().map(OrderItemVo::getSkuId).collect(Collectors.toList());

        Map<Long, SpuInfoVo> spuInfoVoMap = productFeignService.listSpuInfoBySkuIds(skuIds).getData(new TypeReference<Map<Long, SpuInfoVo>>() {});

        Map<Long, SpuBoundVo> boundVoMap = couponFeignService.listSpuBoundBySkuIds(skuIds).getData(new TypeReference<Map<Long, SpuBoundVo>>() {});

        OrderEntity orderEntity = createOrderEntity(submitVo, memberVo, orderItemVos, boundVoMap);

        createOrderItem(orderEntity, orderItemVos, spuInfoVoMap, boundVoMap);

        LockStoreRespVo lockStoreRespVo = lockStore(orderItemVos, orderEntity);

        //15分钟未支付关单
        rabbitTemplate.convertAndSend(MqQueueConstant.DEFAULT_DIRECT_EXCHANGE, MqQueueConstant.ORDER_CANCEL_DELAYED_QUEUE, JSONUtil.toJsonStr(lockStoreRespVo));
        //库存服务分布式事务最终一致性
        rabbitTemplate.convertAndSend(MqQueueConstant.DEFAULT_DIRECT_EXCHANGE, MqQueueConstant.STORE_UNLOCK_DELAYED_QUEUE, JSONUtil.toJsonStr(lockStoreRespVo));
        return SubmitOrderResponseVo.builder().order(orderEntity).build();
    }

    public LockStoreRespVo lockStore(List<OrderItemVo> orderItemVos, OrderEntity orderEntity) {
        Map<Long, WareSkuVo> wareSkuVoMap = orderItemVos.stream().collect(Collectors.toMap(
                OrderItemVo::getSkuId,
                orderItemVo -> WareSkuVo.builder()
                        .skuName(orderItemVo.getTitle())
                        .stock(orderItemVo.getCount())
                        .build()));

        LockStoreVo lockStoreVo = LockStoreVo.builder()
                .orderSn(orderEntity.getOrderSn())
                .orderId(orderEntity.getId())
                .LockStockMap(wareSkuVoMap)
                .build();

        R r = wareFeignService.lockStore(lockStoreVo);
        if (!r.getCode().equals("0")) {
            throw new RuntimeException(r.getMsg());
        }

        return r.getData(LockStoreRespVo.class);
    }

    public void createOrderItem(OrderEntity orderEntity, List<OrderItemVo> orderItemVos, Map<Long, SpuInfoVo> spuInfoVoMap, Map<Long, SpuBoundVo> boundVoMap) {
        List<OrderItemEntity> orderItemEntities = new ArrayList<>();
        for (OrderItemVo orderItemVo : orderItemVos) {
            SpuInfoVo spuInfoVo = spuInfoVoMap.get(orderItemVo.getSkuId());
            SpuBoundVo spuBoundVo = boundVoMap.get(orderItemVo.getSkuId());
            OrderItemEntity orderItem = OrderItemEntity.builder()
                    .orderId(orderEntity.getId())
                    .orderSn(orderEntity.getOrderSn())
                    .spuId(spuInfoVo.getId())
                    .spuName(spuInfoVo.getSpuName())
                    .spuBrand(String.valueOf(spuInfoVo.getBrandId()))
                    .categoryId(spuInfoVo.getCatalogId())
                    .skuId(orderItemVo.getSkuId())
                    .skuName(orderItemVo.getTitle())
                    .skuPic(orderItemVo.getPrice().toString())
                    .skuQuantity(orderItemVo.getCount())
                    .skuAttrsVals(JSONUtil.toJsonStr(orderItemVo.getSkuAttr()))
                    .build();
            if (spuBoundVo != null) {
                orderItem.setGiftIntegration(spuBoundVo.getBuyBounds().intValue() * orderItemVo.getCount());
                orderItem.setGiftGrowth(spuBoundVo.getGrowBounds().intValue() * orderItemVo.getCount());
            }
            orderItemEntities.add(orderItem);
        }

        orderItemService.saveBatch(orderItemEntities);
    }


    public OrderEntity createOrderEntity(OrderSubmitVo vo, MemberVo memberVo, List<OrderItemVo> orderItemVos, Map<Long, SpuBoundVo> boundVoMap) {
        MemberAddressVo addressVo = memberFeignService.deliveryAddressById(vo.getAddrId()).getData(MemberAddressVo.class);

        double sum = orderItemVos.stream().map(OrderItemVo::getTotalPrice).mapToDouble(BigDecimal::doubleValue).sum();

        Integer growBounds = orderItemVos.stream()
                .mapToInt(orderItemVo -> {
                    SpuBoundVo spuBoundVo = boundVoMap.get(orderItemVo.getSkuId());
                    return Optional.ofNullable(spuBoundVo)
                            .map(spuBound -> spuBound.getGrowBounds().intValue() * orderItemVo.getCount())
                            .orElse(0);
                })
                .sum();
        Integer buyBounds = orderItemVos.stream()
                .mapToInt(orderItemVo -> {
                    SpuBoundVo spuBoundVo = boundVoMap.get(orderItemVo.getSkuId());
                    return Optional.ofNullable(spuBoundVo)
                            .map(spuBound -> spuBound.getBuyBounds().intValue() * orderItemVo.getCount())
                            .orElse(0);
                })
                .sum();

        OrderEntity order = OrderEntity.builder()
                .memberId(memberVo.getId())
                .orderSn(IdWorker.getTimeId())
                .createTime(LocalDateTime.now())
                .memberUsername(memberVo.getUsername())
                .totalAmount(new BigDecimal(sum))
                .status(0)
                .integration(growBounds)
                .growth(buyBounds)
                .receiverPhone(addressVo.getPhone())
                .receiverPostCode(addressVo.getPostCode())
                .receiverProvince(addressVo.getProvince())
                .receiverCity(addressVo.getCity())
                .receiverRegion(addressVo.getRegion())
                .receiverDetailAddress(addressVo.getDetailAddress())
                .build();

        save(order);
        return order;
    }

}













